<?php
	if(!function_exists('json_encode')) // only PHP >= 5.2.0 has native JSON functions
		require_once('Services_JSON.class.php');

	/**
	 * @author Matthew Lieder
	 * @version 1.0
	 */
	class QExtGrid extends QPaginatedControl {
		protected $blnEditable = false;
		protected $intUniqueIdColumnIndex = 0;
		protected $intAutoExpandColumnIndex = null;
		protected $intSortColumnIndex = 0;
		protected $intSortDirection = 0;
		
		protected $blnManualRender;
		
		protected $strPersisterMethod;
		protected $objPersisterControl;
		protected $objColumnArray;		
		
        protected $blnIsBlockElement = true;
        protected $strJavaScripts = 'ext_grid.js';

        
        public function __construct($objParentObject, $blnManualRender = false, $strControlId = null) {
			// First, call the parent to do most of the basic setup
            try {
                parent::__construct($objParentObject, $strControlId);
            } catch (QCallerException $objExc) {
                $objExc->IncrementOffset();
                throw $objExc;
            }
            
            $this->blnManualRender = $blnManualRender;
            $this->objColumnArray = array();
        }
        
		public function AddColumn(QExtGridColumn $objColumn) {
			//$this->blnModified = true;
			array_push($this->objColumnArray, $objColumn);
		}
		
		public function GetColumn($strIndex) {
			return $this->objColumnArray[$strIndex];
		}
		
		public function GetColumnIndexById($strId) {
			foreach($this->objColumnArray as $id => $objColumn)
				if($objColumn->Name == $strId)
					return $id;
			
			return null;
		}
        
		public function SetPersister($strMethodName, $objParentControl = null) {
			$this->strPersisterMethod = $strMethodName;
			$this->objPersisterControl = $objParentControl;
		}
		
		// Taken from QDataGridBase and modified slightly
		// Used upon rendering to find backticks and perform PHP eval's
		protected function ParseColumnHtml($objColumn, $objObject) {
			$_ITEM = $objObject;
			$_FORM = $this->objForm;
			$_CONTROL = $this;
			$_COLUMN = $objColumn;

			$strHtml = $objColumn->Html;
			$intPosition = 0;
			
			while (($intStartPosition = strpos($strHtml, '<?=', $intPosition)) !== false) {
				$intEndPosition = strpos($strHtml, '?>', $intStartPosition);
				if ($intEndPosition === false)
					return $strHtml;
				$strToken = substr($strHtml, $intStartPosition + 3, ($intEndPosition - $intStartPosition) - 3);
				$strToken = trim($strToken);
				
				if ($strToken) {
					// Because Eval doesn't utilize exception management, we need to do hack thru the PHP Error Handler
					set_error_handler("DataGridEvalHandleError");
					global $__exc_dtg_errstr;
					$__exc_dtg_errstr = sprintf("Incorrectly formatted DataGridColumn HTML in %s: %s", $this->strControlId, $strHtml);

					try {
						$strEvaledToken = eval(sprintf('return %s;', $strToken));
					} catch (QCallerException $objExc) {
						$objExc->DecrementOffset();
						throw $objExc;
					}

					// Restore the original error handler
					set_error_handler("QcodoHandleError");
					$__exc_dtg_errstr = null;
					unset($__exc_dtg_errstr);
				} else {
					$strEvaledToken = '';
				}
				
				if(is_array($strEvaledToken))
					return $strEvaledToken;

				$strHtml = sprintf("%s%s%s",
					substr($strHtml, 0, $intStartPosition),
					$strEvaledToken,
					substr($strHtml, $intEndPosition + 2));

				$intPosition = $intStartPosition + strlen($strEvaledToken);
			}

			return $strHtml;
		}
		
        public function ParsePostData() {
        	if (array_key_exists('Qform__FormControl', $_POST) && ($_POST['Qform__FormControl'] == $this->strControlId)) {
				if ($_POST['Qform__FormEvent'] == 'QChangeEvent') {
					if(function_exists('json_decode')) {
						$objRecord = json_decode($_POST['Qform__FormParameter'], true);
					} else {
						$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
						$objRecord = $json->decode(stripslashes($_POST['Qform__FormParameter']));
					}
					
					// Run the Persister (if applicable)
					if ($this->objDataSource === null && $this->strPersisterMethod) {
						try {
							$strMethodName = $this->strPersisterMethod;
							if ($this->objPersisterControl)
								$this->objPersisterControl->$strMethodName($objRecord);
							else
								$this->$strMethodName($objRecord);
						} catch (QCallerException $objExc) {
							$objExc->IncrementOffset();
							throw $objExc;
						}
					}
				} else if ($_POST['Qform__FormEvent'] == 'QExtLoadEvent') {
					if(function_exists('json_decode')) {
						$arrParams = json_decode(stripslashes($_POST['Qform__FormParameter']), true);
					} else {
						$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
						$arrParams = $json->decode(stripslashes($_POST['Qform__FormParameter']));
					}
					
					if(array_key_exists('start', $arrParams))
		        		$this->objPaginator->PageNumber = $arrParams['start'] / $this->ItemsPerPage + 1;
		        		
		        	if(array_key_exists('sort', $arrParams))
		        		$this->intSortColumnIndex = $this->GetColumnIndexById($arrParams['sort']);
		        		
		        	if(array_key_exists('dir', $arrParams))
		        		$this->intSortDirection = $arrParams['dir'] == 'DESC' ? 1 : 0;
					
					QApplication::ExecuteJavaScript(sprintf("window.%s('%s');",
						$arrParams['cb'],
						$this->GetJsonData()));
				}
        	}
        }

        protected function GetControlHtml() {
			$strAttributes = $this->GetAttributes();
			$strStyles = $this->GetStyleAttributes();

			if ($strStyles)
				$strStyles = sprintf(' style="%s"', $strStyles);

			return sprintf('<div id="%s" %s%s></div>',
				$this->strControlId,
				$strAttributes,
				$strStyles);
        }
        
        public function GetEndScript() {
        	$strFormId = $this->objForm->FormId;
        	$strWaitIconId = $this->objForm->DefaultWaitIcon->ControlId;
        	$strGridClass = $this->blnEditable ? 'Ext.grid.AdvEditorGrid' : 'Ext.grid.Grid';
        	
        	$intStartRow = ($this->objPaginator->PageNumber - 1) * $this->ItemsPerPage;
        	$strSortCol = $this->objColumnArray[$this->intSortColumnIndex]->Name;
        	$strSortDir = $this->intSortDirection ? 'DESC' : 'ASC';
        	
        	$strScript = <<<EOT
var $this->strControlId = {
init: function() {
	var fm = Ext.form, Ed = Ext.grid.GridEditor;

    // create the Data Store
    var ds = new Ext.data.Store({
        // load using HTTP
        proxy: new Ext.data.QcodoProxy('$this->ControlId', '$strWaitIconId'),

         // the return will be XML, so lets set up a reader
        reader: new Ext.data.JsonReader({
			// records will have an "row" tag
				root: 'rows',
				totalProperty: 'totalRecords'
EOT;
			if($this->intUniqueIdColumnIndex !== null)
				$strScript .= ",\n\t\t\t\tid: '" . $this->objColumnArray[$this->intUniqueIdColumnIndex]->Name . "'";
			
			$strScript .= "\n\t\t\t}, [";

        	foreach ($this->objColumnArray as $objColumn) {
        		$strScript .= "\n\t\t\t\t{name: '$objColumn->Name'";
        		
				if($objColumn->Type)
					$strScript .= ", type: '$objColumn->Type'";
					
				if($objColumn->DateFormat)
					$strScript .= ", dateFormat: '$objColumn->DateFormat'";
				
				$strScript .= "},";
        	}
        	$strScript = rtrim($strScript, ',');
        	
			$strScript .= <<<EOT
\n			]),

		remoteSort: true
	});

	ds.on("update", function (store, record, op) {
		if(op == Ext.data.Record.COMMIT)
			qc.pA('$strFormId', '$this->strControlId',
				'QChangeEvent', Ext.util.JSON.encode(record.data), '$strWaitIconId');
	});

	var cm = new Ext.grid.ColumnModel([
EOT;
			foreach ($this->objColumnArray as $objColumn) {
				if($objColumn->Header !== null)
				{
					$strScript .= sprintf("\n\t\t{id: '%s', header: '%s', width: %d, dataIndex: '%s'",
						strtolower($objColumn->Name),
						$objColumn->Header,
						$objColumn->Width,
						$objColumn->Name);
						
					if($objColumn->Hidden)
						$strScript .= ", hidden: true";
					
					if($objColumn->OrderByClause)
						$strScript .= ", sortable: true";
					
					// for some reason this isn't taking effect, though it should...
					//$strScript .= ", css: 'white-space: normal !important;'";
						
					if($objColumn->Renderer)
						$strScript .= ", renderer: " . $objColumn->Renderer;
	
					if($this->blnEditable && $objColumn->Editor)
						$strScript .= ", editor: new Ed($objColumn->Editor)";
												
					$strScript .= "},";
				}
			}
			$strScript = rtrim($strScript, ',');

			$strScript .= <<<EOT
\n	]);

    // create the grid
    var grid = new $strGridClass('$this->ControlId', {
        ds: ds,
        cm: cm,
		selModel: new Ext.grid.RowSelectionModel({singleSelect: true}),
		loadMask: true,
		trackMouseOver: true,
EOT;
			if($this->intAutoExpandColumnIndex !== null && $this->intAutoExpandColumnIndex >= 0)
				$strScript .= "\n\tautoExpandColumn: '"
					. strtolower($this->objColumnArray[$this->intAutoExpandColumnIndex]->Name) . "',";
			$strScript = rtrim($strScript, ',');

			$strScript .= <<<EOT
\n    });
    grid.render();

	grid.on("afterEdit", function(e) {
		e.record.commit();
	});

    var gridFoot = grid.getView().getFooterPanel(true);

    // add a paging toolbar to the grid's footer
    var paging = new Ext.PagingToolbar(gridFoot, ds, {
        pageSize: $this->ItemsPerPage,
        displayInfo: true,
        displayMsg: 'Displaying $this->NounPlural {0} - {1} of {2}',
        emptyMsg: "No $this->NounPlural to display"
    });

    ds.load({params: {start: $intStartRow, sort: '$strSortCol', dir: '$strSortDir'}});

	this.grid = grid;
	this.ds = ds;
	this.paging = paging;
}};
EOT;
			if(!$this->blnManualRender)
				$strScript .= "Ext.onReady($this->strControlId.init, $this->strControlId);";

			return $strScript;
        }
        
        private function GetRowArray($objRow) {
            // Iterate through the Columns
			$arrColumns = array();
			foreach ($this->objColumnArray as $objColumn) {
				try {
					$strHtml = $this->ParseColumnHtml($objColumn, $objRow);
				} catch (QCallerException $objExc) {
					$objExc->IncrementOffset();
					throw $objExc;
				}
				$arrColumns[$objColumn->Name] = $strHtml;
			}
			
			return $arrColumns;
        }
        
        protected function GetJsonData() {
			// Run the DataBinder (if applicable)
			if (($this->objDataSource === null) && ($this->strDataBindMethod))
				try {
					$this->objForm->CallDataBinder($this->strDataBindMethod, $this->objDataBindControl);
				} catch (QCallerException $objExc) {
					$objExc->IncrementOffset();
					throw $objExc;
				}
			
			$arrRows = array();
			if ($this->objDataSource)
				foreach ($this->objDataSource as $objObject)
					$arrRows[] = $this->GetRowArray($objObject);
			
			$arrResponse = array();
			$arrResponse['totalRecords'] = $this->TotalItemCount;
			$arrResponse['rows'] = $arrRows;
			
			$this->objDataSource = null;
			
			if(function_exists('json_encode')) {
				return addslashes(json_encode($arrResponse));
			} else {
				$json = new Services_JSON();
				return addslashes($json->encode($arrResponse));
			}
        }
        
        public function RefreshData() {
        	$this->objPaginator->PageNumber = 1;
        	
			QApplication::ExecuteJavaScript(sprintf("%s.grid.getDataSource().removeAll(); %s.grid.getDataSource().loadData(eval('('+'%s'+')'));",
				$this->strControlId,
				$this->strControlId,
				$this->GetJsonData()));
        }
        
		/////////////////////////
		// Public Properties: GET
		/////////////////////////
		public function __get($strName) {
			switch ($strName) {
				case 'Editable': return $this->blnEditable;
				
				case 'UniqueIdColumnIndex': return $this->intUniqueIdColumnIndex;
				case 'AutoExpandColumnIndex': return $this->intAutoExpandColumnIndex;
				
				case "SortColumnIndex": return $this->intSortColumnIndex;
				case "SortDirection": return $this->intSortDirection;
				
				case "ManualRender": return $this->blnManualRender;
				
				case "OrderByClause":
					if ($this->intSortColumnIndex >= 0) {
						if ($this->intSortDirection == 0)
							return $this->objColumnArray[$this->intSortColumnIndex]->OrderByClause;
						else
							return $this->objColumnArray[$this->intSortColumnIndex]->ReverseOrderByClause;
					} else
						return null;
						
				default:
					try {
						return parent::__get($strName);
					} catch (QCallerException $objExc) {
						$objExc->IncrementOffset();
						throw $objExc;
					}
			}
		}

		/////////////////////////
		// Public Properties: SET
		/////////////////////////
		public function __set($strName, $mixValue) {
			switch ($strName) {
				case "Editable":
					try {
						$this->blnEditable = QType::Cast($mixValue, QType::Boolean);
						break;
					} catch (QInvalidCastException $objExc) {
						$objExc->IncrementOffset();
						throw $objExc;
					}
				case "UniqueIdColumnIndex":
					try {
						$this->intUniqueIdColumnIndex = QType::Cast($mixValue, QType::String);
						break;
					} catch (QInvalidCastException $objExc) {
						$objExc->IncrementOffset();
						throw $objExc;
					}
				case "AutoExpandColumnIndex":
					try {
						$this->intAutoExpandColumnIndex = QType::Cast($mixValue, QType::String);
						break;
					} catch (QInvalidCastException $objExc) {
						$objExc->IncrementOffset();
						throw $objExc;
					}

				case "SortColumnIndex":
					try {
						$this->intSortColumnIndex = QType::Cast($mixValue, QType::Integer);
						break;
					} catch (QInvalidCastException $objExc) {
						$objExc->IncrementOffset();
						throw $objExc;
					}

				case "SortDirection":
					if ($mixValue == 1)
						$this->intSortDirection = 1;
					else
						$this->intSortDirection = 0;
					break;
				
					
				default:
					try {
						parent::__set($strName, $mixValue);
						break;
					} catch (QCallerException $objExc) {
						$objExc->IncrementOffset();
						throw $objExc;
					}
			}
			
			$this->blnModified = false;
		}
	}
	
	class QExtLoadEvent extends QEvent {}
	
	// Taken from QDataGridBase
	function DataGridEvalHandleError($__exc_errno, $__exc_errstr, $__exc_errfile, $__exc_errline) {
		$__exc_objBacktrace = debug_backtrace();
		for ($__exc_intIndex = 0; $__exc_intIndex < count($__exc_objBacktrace); $__exc_intIndex++) {
			$__exc_objItem = $__exc_objBacktrace[$__exc_intIndex];

			if ((strpos($__exc_errfile, "QExtGrid.class.php") !== false) &&
				(strpos($__exc_objItem["file"], "QExtGrid.class.php") === false)) {
				$__exc_errfile = $__exc_objItem["file"];
				$__exc_errline = $__exc_objItem["line"];
			} else if ((strpos($__exc_errfile, "QFormBase.class.php") !== false) &&
				(strpos($__exc_objItem["file"], "QFormBase.class.php") === false)) {
				$__exc_errfile = $__exc_objItem["file"];
				$__exc_errline = $__exc_objItem["line"];
			}
		}

		global $__exc_dtg_errstr;
		if (isset($__exc_dtg_errstr) && ($__exc_dtg_errstr))
			$__exc_errstr = sprintf("%s\n%s", $__exc_dtg_errstr, $__exc_errstr);
		QcodoHandleError($__exc_errno, $__exc_errstr, $__exc_errfile, $__exc_errline);
	}
?>